package com.jf.anmocker.plugin.mockcore.asm

import com.jf.mocker.anotations.IMethodMocker
import com.jf.mocker.anotations.MethodMachWay
import com.jf.mocker.anotations.MethodMockType
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.AnnotationNode

class MethodAnoNode(private val descriptor:String?, val api : Int = Opcodes.ASM9) : AnnotationNode(api, descriptor) {

    private var methodMockInfo : MethodAnoEntity? = null
    private var isMethodMocker  = false
    private var listener : MethodAnoVisitListener? = null

    override fun visit(name: String?, value: Any?) {
        super.visit(name, value)
    }

    override fun visitArray(name: String?): AnnotationVisitor {
        val node:AnnotationNode= super.visitArray(name) as AnnotationNode;
        //println("visitArray $name >>> ${node.values}")
        return node
    }

    override fun visitEnum(name: String?, descriptor: String?, value: String?) {
        //println("visitEnum $name >>> $value  descriptor: $descriptor")
        if (values == null) {
            values = ArrayList(if (desc != null) 2 else 1)
        }
        if (desc != null) {
            values.add(name)
        }
        if(descriptor != null && value != null){
            values.add(listOf(descriptor, value))
        }
    }

    override fun visitEnd() {
        super.visitEnd()
        if(Type.getDescriptor(IMethodMocker::class.java).equals(descriptor)){
            isMethodMocker = true
            updateIMethodMockerInfo()
        }
        methodMockInfo?.let {
            println("anoEntity : $it")
        }
        listener?.methodVisited(isMethodMocker, methodMockInfo)
    }

    public fun setMethodVisitListener(listener : MethodAnoVisitListener?){
        this.listener = listener
    }

    public fun isMethodMocker() : Boolean{
        return isMethodMocker
    }

    public fun getMethodMockInfo() : MethodAnoEntity?{
        return methodMockInfo
    }

    private fun updateIMethodMockerInfo(){
        if(methodMockInfo == null){
            methodMockInfo = MethodAnoEntity()
        }
        //println("updateIMethodMockerInfo $name >>> $value")
        for (index in values.indices step 2){
            if(values[index] is String && index+1 < values.size){
                readValue(values[index] as String, values[index+1])
            }else{
                println("${values[index]} value is null")
            }
        }
    }

    private fun readValue(name:String, value: Any?){
        when(name){
            "name" -> {
                methodMockInfo?.name = getValue(value)
            }
            "params" -> {
                //println("params >>> $value")
                methodMockInfo?.params = getParamsValue(value)
            }
            "machWay" -> {
                //println("machWay >>> $value")
                methodMockInfo?.machWay = getMethodMachWayValue(value)
            }
            "mockType" -> {
                //println("mockType >>> $value")
                methodMockInfo?.mockType = getMethodMockTypeValue(value)
            }
            "priority" -> {
                methodMockInfo?.priority = getValue(value, 0)
            }
        }
    }

    private inline fun <reified T> getValue(value: Any?) : T? {
        //println("getValue >>> $value Type is ${value?.let {Type.getDescriptor(it.javaClass)}}")
        if(value == null){
            return null
        }else if(value is T){
            return value
        }
        return null
    }

    private inline fun <reified T> getValue(value: Any?, defaultValue : T) : T {
        //println("getValue >>> $value Type is ${value?.let {Type.getDescriptor(it.javaClass)}}")
        if(value == null){
            return defaultValue
        }else if(value is T){
            return value
        }
        return defaultValue
    }

    private fun getParamsValue(value: Any?) : ArrayList<String>?{
        if(value != null && value is ArrayList<*>){
            return value.filterIsInstance<String>() as ArrayList<String>
        }
        return null
    }

    private fun getMethodMachWayValue(value: Any?) : MethodMachWay {
        if (value == null) {
            return MethodMachWay.ONLY
        } else if (value is MutableList<*>) {
            if (value.size != 2) {
                return MethodMachWay.ONLY
            }
            if (value[0] is String && value[1] is String) {
                if (value[0] == Type.getDescriptor(MethodMachWay::class.java)) {
                    return MethodMachWay.valueOf(value[1] as String)
                }
                return MethodMachWay.ONLY
            }
        }
        return MethodMachWay.ONLY
    }

    private fun getMethodMockTypeValue(value: Any?) : MethodMockType {
        if (value == null) {
            return MethodMockType.Replace
        } else if (value is MutableList<*>) {
            if (value.size != 2) {
                return MethodMockType.Replace
            }
            if (value[0] is String && value[1] is String) {
                if (value[0] == Type.getDescriptor(MethodMockType::class.java)) {
                    return MethodMockType.valueOf(value[1] as String)
                }
                return MethodMockType.Replace
            }
        }
        return MethodMockType.Replace
    }

}

interface MethodAnoVisitListener{
    fun methodVisited(isMethodMocker : Boolean, methodMockInfo : MethodAnoEntity?)
}